package com.ljq.ioc.core;


import com.ljq.ioc.annotation.Autowired;
import com.ljq.ioc.annotation.Bean;
import com.ljq.ioc.annotation.Component;
import com.ljq.ioc.annotation.Qualifier;
import com.ljq.ioc.excptions.InjectFailException;
import com.ljq.ioc.excptions.noSuchBeanException;
import com.ljq.ioc.util.PackageScanner;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ioc核心，三大功能：
 * 1. scanBeanByPackage：扫描所有包，启动容器
 * 2. 依赖注入
 * 3. getBean：获取实例
 */
public class BeanFactory {

    private final Map<Class<?>, BeanDefinition> beanPool;//bean池(类)，通过类型获取
    private final Map<String, Class<?>> alias;//通过类名获取类型
    private final Map<Class<?>, BeanMethodDefinition> beanMethodPool;//bean池（方法），通过类型获取
    private final Map<String, Class<?>> methodAlias;

    static {
    }

    public BeanFactory() {
        this.beanPool = new HashMap<Class<?>, BeanDefinition>();
        this.alias = new HashMap<String, Class<?>>();
        this.beanMethodPool = new HashMap<Class<?>, BeanMethodDefinition>();
        this.methodAlias = new HashMap<String, Class<?>>();
    }

    /**
     * 根据包名进行扫描，并且启动所有标注@Component的容器
     *
     * @param packageName
     */
    public void scanBeanByPackge(String packageName) {
        new PackageScanner() {
            //扫描包下的所有类
            public void dealClass(Class<?> klass) throws IllegalAccessException, InstantiationException {
                //判断类是否被@component标注
                if (!klass.isAnnotationPresent(Component.class)) {
                    return;
                }
                //既然被Component标注就需要启动容器，即新建一个实例放入beanPool
                try {
                    Object object = klass.newInstance();
                    BeanDefinition bd = new BeanDefinition();
                    bd.setKlass(klass);
                    bd.setObject(object);
                    beanPool.put(klass, bd);
                    //先从Component找容器的名字
                    Component component = klass.getAnnotation(Component.class);
                    String alia = component.name();
                    //判断一下component是否命名了, 未命名则通过类获取名字
                    if (alia.length() == 0) {
                        alia = klass.getSimpleName();
                    }
                    alias.put(alia, klass);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                //注入方法的bean
                Method[] methods = klass.getDeclaredMethods();
                for (Method method : methods) {
                    if (!method.isAnnotationPresent(Bean.class)) {
                        continue;
                    }
                    Object object = method.getReturnType().newInstance();
                    BeanMethodDefinition bmd = new BeanMethodDefinition();
                    bmd.setKlass(method.getReturnType());
                    bmd.setMethod(method);
                    bmd.setObject(object);
                    bmd.getMethod();
                    String methodName = method.getDeclaredAnnotation(Bean.class).name();
                    methodAlias.put(methodName.length() == 0 ? method.getReturnType().getSimpleName() : methodName, method.getReturnType());
                    beanMethodPool.put(method.getReturnType(), bmd);
                }

            }
        }.scanPackage(packageName);
    }

    /**
     * 依赖注入：为所有的容器注入依赖，需要避免循环依赖
     *
     * @param bd
     */
    private void injectProperties(BeanDefinition bd) throws InjectFailException, IllegalAccessException, InvocationTargetException, InstantiationException {
        System.err.println("开始注入" + bd.getKlass().getSimpleName() + "依赖");
        Class<?> klass = bd.getKlass();
        Object object = bd.getObject();

        //获取该类的成员变量
        Field[] fields = klass.getDeclaredFields();
        for (Field field : fields) {
            //只处理@Autowired的成员变量
            if (!field.isAnnotationPresent(Autowired.class)) {
                continue;
            }
            //在beanFactory中查找是否有该成员变量的容器
            //首先拿到该成员变量的类型
            Class<?> prototyklass = field.getType();
            //根据类型拿到该成员变量的beanDefinition
            BeanDefinition beanDefinition = getBeanDefinition(prototyklass);
            //如果无法通过类型获取 如果该成员变量通过@Qualifier指定了依赖 则注入该依赖
            if (beanDefinition == null) {
                //如果该成员变量通过@Qualifier指定了依赖，则去寻找该依赖
                if (field.isAnnotationPresent(Qualifier.class)) {
                    Qualifier qualifier = field.getAnnotation(Qualifier.class);
                    String implementationClass = qualifier.ImplementationClass();
                    if (implementationClass.length() == 0) {
                        System.err.println(bd.getKlass().getSimpleName() + ": 无法根据@Qualifier进行注入，因为没有为@Qualifier指定属性");
                        break;
                    }
                    beanDefinition = getBeanDefinition(implementationClass);
                }
                //说明注入成功
                if (beanDefinition != null) {

                } else {
                    //如果没有通过Qualifier注入依赖，则在beanFactory寻找他的父类或者继承类
                    BeanDefinition[] beans = searchBeanBySuperType(klass);
                    if (beans.length == 0) {
                        System.err.println(bd.getKlass().getSimpleName() + "无法找到对应的依赖");
                        throw new InjectFailException(bd.getKlass().getSimpleName() + "无法找到对应的依赖");
                    }
                    if (beans.length > 1) {
                        System.err.println(bd.getKlass().getSimpleName() + "无法正确注入依赖，因为对应的依赖存在多个");
                        throw new InjectFailException(bd.getKlass().getSimpleName() + "无法正确注入依赖，因为对应的依赖存在多个");
                    }
                    beanDefinition = beans[0];
                }
            }
            Object value = getBean(beanDefinition.getKlass());

            //给成员赋值
            try {
                field.setAccessible(true);
                field.set(object, value);
            } catch (IllegalAccessException e) {
                throw new InjectFailException(klass.getSimpleName() + "的成员" + field.getName() + "注入失败");
            }
        }
    }

    /**
     * 为方法bean注入依赖
     * 循环依赖的解决方法：在为类注入依赖前，生成一个Map<方法名，List<参数名>> 由于每为其寻找一次参数依赖就
     * 会进行一次getBean，因此每次getBean使 BeanMethodDefinition.paraCount-1，并且从Map中移除该参数名，当
     * paraCount==0时，如果Map中方法对应的List<参数名>不是空的，则说明存在循环依赖->因为进入循环时始终在操作
     * 一个参数
     *
     * @param beanMethodDefinition
     */
    private void injectProperties(BeanMethodDefinition beanMethodDefinition) throws InjectFailException, IllegalAccessException, InvocationTargetException, InstantiationException {
//        System.err.println("injectProperties");
        Method method = beanMethodDefinition.getMethod();
        Class<?> klass = beanMethodDefinition.getKlass();
        Object object = beanMethodDefinition.getObject();
        for (Parameter parameter : method.getParameters()) {
            Class<?> paraClass = parameter.getType();
            beanMethodDefinition.decrease();
            if (beanMethodDefinition.getParaCount() < 0) {
                throw new InjectFailException("存在循环依赖");
            }
            Object value = getBean(paraClass);
            //判断该参数是否已经被准备
        }
    }

    /**
     * 根据类型获取bean的对象 同时改变bean的属性 如果bean的依赖未注入 需要在取出前进行注入
     *
     * @param klass
     * @param <T>
     * @return
     */
    public <T> T getBean(Class<?> klass) throws InjectFailException, IllegalAccessException, InvocationTargetException, InstantiationException {
        BeanDefinition beanDefinition = getBeanDefinition(klass);
        Object object;
        if (beanDefinition == null) {
            //可能是一个方法bean
            BeanMethodDefinition beanMethodDefinition = getBeanMethodDefinition(klass);
            if (beanMethodDefinition == null) {
                throw new noSuchBeanException("类[" + klass.getSimpleName() + "]没有对应的bean");
            }
            object = beanMethodDefinition.getObject();
            if (beanMethodDefinition.getParaCount() > 0) {
                injectProperties(beanMethodDefinition);
            }
            Method method = beanMethodDefinition.getMethod();
            List<Object> objects = new ArrayList<>();
            for (Parameter parameter : method.getParameters()) {
                BeanDefinition bd = getBeanDefinition(parameter.getType());
                if (bd == null) {
                    throw new InjectFailException("存在循环依赖");
                }
                objects.add(bd.getObject());
            }
            object = method.invoke(method.getDeclaringClass().newInstance(), objects.toArray(new Object[objects.size()]));
            BeanDefinition newBean = new BeanDefinition();
            newBean.setInject(true);
            newBean.setObject(object);
            newBean.setKlass(object.getClass());
            beanPool.put(newBean.getKlass(), newBean);
            alias.put(newBean.getKlass().getSimpleName(), klass);
        } else {
            object = beanDefinition.getObject();
            //未注入时需要进行注入
            if (!beanDefinition.isInject()) {
                beanDefinition.setInject(true);
                injectProperties(beanDefinition);
            }
        }
        return (T) object;
    }


    /**
     * 根据类型寻找beanDefinition
     *
     * @param klass
     * @return
     */
    private BeanDefinition getBeanDefinition(Class<?> klass) {
        return beanPool.get(klass);
    }

    /**
     * 根据类名寻找beanDefinition
     *
     * @param className
     * @return
     */
    private BeanDefinition getBeanDefinition(String className) {
        return beanPool.get(alias.get(className));
    }

    /**
     * 根据类型找方法bean
     *
     * @param klass
     * @return
     */
    private BeanMethodDefinition getBeanMethodDefinition(Class<?> klass) {
        return beanMethodPool.get(klass);
    }

    /**
     * 根据类名找方法bean
     *
     * @param className
     * @return
     */
    private BeanMethodDefinition getBeanMethodDefinition(String className) {
        return beanMethodPool.get(methodAlias.get(className));
    }

    /**
     * 寻找一个类的继承类
     *
     * @param klass
     * @return
     */
    private BeanDefinition[] searchBeanBySuperType(Class<?> klass) {
        List<BeanDefinition> beanList = new ArrayList<BeanDefinition>();
        for (BeanDefinition bd : beanPool.values()) {
            if (bd.getKlass() == klass) continue;
            if (klass.isAssignableFrom(bd.getKlass())) {
                beanList.add(bd);
            }
        }
        return beanList.toArray(new BeanDefinition[beanList.size()]);
    }

}
